﻿using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Newtonsoft.Json.Linq;
using Sparklings.JsonUtils;
using Sparklings.RayTracing;
using Sparklings.Rendering;
using System;
using System.Collections.Generic;
using System.Reflection;

namespace Sparklings.LevelElements
{
    public abstract class Tile : IRenderable
    {
        public Tile(Level level, int x, int y, bool isOccluder)
        {
            this.Level = level;
            this.x = x;
            this.y = y;
            this.isOccluder = isOccluder;
            this.HitColor = RayColor.None;
            lightSurfaces = new List<LightHittable>();
            lightSurfaces.Add(new LightHittable(PositionULCorner, PositionURCorner, isOccluder));
            lightSurfaces.Add(new LightHittable(PositionURCorner, PositionLRCorner, isOccluder));
            lightSurfaces.Add(new LightHittable(PositionLRCorner, PositionLLCorner, IsOccluder));
            lightSurfaces.Add(new LightHittable(PositionLLCorner, PositionULCorner, isOccluder));
            foreach (LightHittable hittable in lightSurfaces)
            {
                hittable.Hit += OnHit;
            }
        }
        /// <summary>
        /// Loads all the content that has a component stored on the graphics card.
        /// 
        /// It will be called after the object construction, potentially before any
        /// update.
        /// </summary>
        public virtual void LoadGraphicsRelatedElements()
        {
        }
        /// <summary>
        /// The surfaces that will be sent for rayTracing. By default, it is all the sides
        /// of the tile. Wether they occlude or not depends on the parameter given to the
        /// Tile constructor.
        /// </summary>
        public virtual IList<LightHittable> LightSurfaces { get { return lightSurfaces; } }
        /// <summary>
        /// Performs an update of the tile state.
        /// </summary>
        /// <param name="gameTime"></param>
        public virtual void Update(GameTime gameTime)
        {

        }
        /// <summary>
        /// Called before update is called, and before raytracing is done.
        /// </summary>
        public virtual void PrepareRayHits()
        {
            HitColor = RayColor.None;
        }
        public virtual void DrawBase(RenderContext rc)
        {
            if (BaseTexture != null)
            {
                rc.Draw(BaseTexture, Center);
            }
        }
        public virtual void DrawLight(RenderContext rc) { }
        public virtual void DrawAdditive(RenderContext rc) { }
        public virtual void DrawEmissive(RenderContext rc) { }
        public virtual void DrawText(RenderContext rc, SpriteBatch b) { }
        public static JToken ConvertToJson(JsonOverrider converter, object value)
        {
            var ret = new JObject();
            ret["type"] = value.GetType().Name;
            var jdata = (value as Tile).GetJsonData(converter);
            ret["data"] = jdata;
            return ret;
        }
        public virtual JToken GetJsonData(JsonOverrider converter)
        {
            return new JObject();
        }
        public static object ConvertFromJson(JsonOverrider converter, JToken toConvert)
        {
            Level level = converter.GetClosestParentWithType<Level>();
            string tileTypeName = converter.Read<string>(toConvert["type"]);
            Type tileType = Type.GetType("Sparklings.LevelElements." + tileTypeName, true);
            if (!tileType.IsSubclassOf(typeof(Tile)))
            {
                throw new TypeLoadException("The type \"" + tileTypeName + "\" is not a subclass of Tile");
            }
            var factory = tileType.GetMethod("ConstructFromData", BindingFlags.Static | BindingFlags.Public);
            var ret = factory.Invoke(null, new object[] {
                converter,
                toConvert["data"],
                level,
                level.LoadingState.currentTileX,
                level.LoadingState.currentTileY
            });
            level.LoadingState.NextTile();
            return ret;
        }
        // Implement this in subclasses.
        //public static object ConstructFromData(JsonOverrider converter, JToken toConvert,Level level,int x, int y);
        public bool IsIn(Vector2 position)
        {
            return (position.X >= PositionULCorner.X && position.X < PositionLRCorner.X)
                && (position.Y >= PositionULCorner.Y && position.Y < PositionLRCorner.Y);
        }
        public IList<Sparkling> GetCurrentlyContainedSparklings()
        {
            return Level.Sparks.FindAll(spark => IsIn(spark.Position));
        }
        public Level Level { get; private set; }
        public Vector2 FloatPosition { get { return new Vector2(X, Y); } }
        public Vector2 Center { get { return FloatPosition + new Vector2(0.5f); } }
        public Vector2 PositionULCorner { get { return FloatPosition; } }
        public Vector2 PositionURCorner { get { return FloatPosition + new Vector2(1, 0); } }
        public Vector2 PositionLLCorner { get { return FloatPosition + new Vector2(0, 1); } }
        public Vector2 PositionLRCorner { get { return FloatPosition + new Vector2(1, 1); } }
        public bool IsOccluder
        {
            get { return isOccluder; }
            set
            {
                isOccluder = value;
                foreach (var lightHittable in LightSurfaces)
                {
                    lightHittable.IsOccluder = value;
                }
            }
        }
        public int X { get { return x; } }
        public int Y { get { return y; } }
        public RayColor HitColor { get; protected set; }
        public abstract bool IsPassableBySparkling(Sparkling c);

        protected void OnHit(ColoredRay from, LightHittable segmentHit, Vector2 positionHit)
        {
            HitColor = HitColor.Combined(from.Color);
        }

        private int x, y;
        private bool isOccluder;
        private IList<LightHittable> lightSurfaces;
        protected Texture2D BaseTexture;
    }
}
